// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE

(function(mod) {
  if (typeof exports == 'object' && typeof module == 'object')
    // CommonJS
    mod(
      require('../../lib/codemirror'),
      require('../markdown/markdown'),
      require('../../addon/mode/overlay'),
    );
  else if (typeof define == 'function' && define.amd)
    // AMD
    define([
      '../../lib/codemirror',
      '../markdown/markdown',
      '../../addon/mode/overlay',
    ], mod);
  // Plain browser env
  else mod(CodeMirror);
})(function(CodeMirror) {
  'use strict';

  var urlRE = /^((?:(?:aaas?|about|acap|adiumxtra|af[ps]|aim|apt|attachment|aw|beshare|bitcoin|bolo|callto|cap|chrome(?:-extension)?|cid|coap|com-eventbrite-attendee|content|crid|cvs|data|dav|dict|dlna-(?:playcontainer|playsingle)|dns|doi|dtn|dvb|ed2k|facetime|feed|file|finger|fish|ftp|geo|gg|git|gizmoproject|go|gopher|gtalk|h323|hcp|https?|iax|icap|icon|im|imap|info|ipn|ipp|irc[6s]?|iris(?:\.beep|\.lwz|\.xpc|\.xpcs)?|itms|jar|javascript|jms|keyparc|lastfm|ldaps?|magnet|mailto|maps|market|message|mid|mms|ms-help|msnim|msrps?|mtqp|mumble|mupdate|mvn|news|nfs|nih?|nntp|notes|oid|opaquelocktoken|palm|paparazzi|platform|pop|pres|proxy|psyc|query|res(?:ource)?|rmi|rsync|rtmp|rtsp|secondlife|service|session|sftp|sgn|shttp|sieve|sips?|skype|sm[bs]|snmp|soap\.beeps?|soldat|spotify|ssh|steam|svn|tag|teamspeak|tel(?:net)?|tftp|things|thismessage|tip|tn3270|tv|udp|unreal|urn|ut2004|vemmi|ventrilo|view-source|webcal|wss?|wtai|wyciwyg|xcon(?:-userid)?|xfire|xmlrpc\.beeps?|xmpp|xri|ymsgr|z39\.50[rs]?):(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]|\([^\s()<>]*\))+(?:\([^\s()<>]*\)|[^\s`*!()\[\]{};:'".,<>?«»“”‘’]))/i;

  CodeMirror.defineMode(
    'gfm',
    function(config, modeConfig) {
      var codeDepth = 0;
      function blankLine(state) {
        state.code = false;
        return null;
      }
      var gfmOverlay = {
        startState: function() {
          return {
            code: false,
            codeBlock: false,
            ateSpace: false,
          };
        },
        copyState: function(s) {
          return {
            code: s.code,
            codeBlock: s.codeBlock,
            ateSpace: s.ateSpace,
          };
        },
        token: function(stream, state) {
          state.combineTokens = null;

          // Hack to prevent formatting override inside code blocks (block and inline)
          if (state.codeBlock) {
            if (stream.match(/^```+/)) {
              state.codeBlock = false;
              return null;
            }
            stream.skipToEnd();
            return null;
          }
          if (stream.sol()) {
            state.code = false;
          }
          if (stream.sol() && stream.match(/^```+/)) {
            stream.skipToEnd();
            state.codeBlock = true;
            return null;
          }
          // If this block is changed, it may need to be updated in Markdown mode
          if (stream.peek() === '`') {
            stream.next();
            var before = stream.pos;
            stream.eatWhile('`');
            var difference = 1 + stream.pos - before;
            if (!state.code) {
              codeDepth = difference;
              state.code = true;
            } else {
              if (difference === codeDepth) {
                // Must be exact
                state.code = false;
              }
            }
            return null;
          } else if (state.code) {
            stream.next();
            return null;
          }
          // Check if space. If so, links can be formatted later on
          if (stream.eatSpace()) {
            state.ateSpace = true;
            return null;
          }
          if (stream.sol() || state.ateSpace) {
            state.ateSpace = false;
            if (modeConfig.gitHubSpice !== false) {
              if (
                stream.match(
                  /^(?:[a-zA-Z0-9\-_]+\/)?(?:[a-zA-Z0-9\-_]+@)?(?=.{0,6}\d)(?:[a-f0-9]{7,40}\b)/,
                )
              ) {
                // User/Project@SHA
                // User@SHA
                // SHA
                state.combineTokens = true;
                return 'link';
              } else if (
                stream.match(
                  /^(?:[a-zA-Z0-9\-_]+\/)?(?:[a-zA-Z0-9\-_]+)?#[0-9]+\b/,
                )
              ) {
                // User/Project#Num
                // User#Num
                // #Num
                state.combineTokens = true;
                return 'link';
              }
            }
          }
          if (
            stream.match(urlRE) &&
            stream.string.slice(stream.start - 2, stream.start) != '](' &&
            (stream.start == 0 ||
              /\W/.test(stream.string.charAt(stream.start - 1)))
          ) {
            // URLs
            // Taken from http://daringfireball.net/2010/07/improved_regex_for_matching_urls
            // And then (issue #1160) simplified to make it not crash the Chrome Regexp engine
            // And then limited url schemes to the CommonMark list, so foo:bar isn't matched as a URL
            state.combineTokens = true;
            return 'link';
          }
          stream.next();
          return null;
        },
        blankLine: blankLine,
      };

      var markdownConfig = {
        taskLists: true,
        strikethrough: true,
        emoji: true,
      };
      for (var attr in modeConfig) {
        markdownConfig[attr] = modeConfig[attr];
      }
      markdownConfig.name = 'markdown';
      return CodeMirror.overlayMode(
        CodeMirror.getMode(config, markdownConfig),
        gfmOverlay,
      );
    },
    'markdown',
  );

  CodeMirror.defineMIME('text/x-gfm', 'gfm');
});
